﻿using System;
using System.Collections.Generic;
using System.Text;
using Sanford.Multimedia.Midi;
using System.Threading;

namespace MidiSynthLib
{
    public class Synth : IDisposable
    {
        private OutputDevice outDevice;
        private int midiChannel;
        private int midiProgram;

        /// <summary>
        /// Initializes a new instance of the <see cref="Synth"/> class.
        /// </summary>
        public Synth()
            : this(0, 0)
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="Synth"/> class.
        /// </summary>
        /// <param name="midiChannel">The midi channel.</param>
        /// <param name="midiProgram">The midi program.</param>
        public Synth(int midiChannel, int midiProgram)
        {
            if (OutputDevice.DeviceCount == 0)
            {
                throw new MidiSynthException("No output devices were found!");
            }
            else
            {
                outDevice = new OutputDevice(0);
                this.midiChannel = midiChannel;
                this.midiProgram = midiProgram;
            }
        }

        /// <summary>
        /// Gets or sets the output device id.
        /// </summary>
        /// <value>The output device id.</value>
        public int OutputDeviceId
        {
            get { return outDevice.DeviceID; }
            set
            {
                outDevice.Close();
                outDevice = new OutputDevice(value);
            }
        }

        /// <summary>
        /// Gets or sets the midi program.
        /// </summary>
        /// <value>The midi program.</value>
        public int MidiProgram
        {
            get
            {
                return midiProgram;
            }
            set
            {
                if (SetProgram(value))
                {
                    midiProgram = value;
                }
            }
        }

        /// <summary>
        /// Gets or sets the midi instrument.
        /// </summary>
        /// <value>The midi instrument.</value>
        public GeneralMidiInstrument MidiInstrument
        {
            get
            {
                return (GeneralMidiInstrument)midiProgram;
            }
            set
            {
                if (SetProgram((int)value))
                {
                    midiProgram = (int)value;
                }
            }
        }

        /// <summary>
        /// Sets the program.
        /// </summary>
        /// <param name="program">The program.</param>
        /// <returns></returns>
        private bool SetProgram(int program)
        {
            bool success = false;
            if (outDevice != null)
            {
                outDevice.Send(new ChannelMessage(ChannelCommand.ProgramChange, midiChannel, program));
                success = true;
            }
            return success;
        }

        /// <summary>
        /// Gets or sets the midi channel.
        /// </summary>
        /// <value>The midi channel.</value>
        public int MidiChannel
        {
            get
            {
                return midiChannel;
            }
            set
            {
                midiChannel = value;
                SetProgram(midiProgram);
            }
        }

        /// <summary>
        /// Plays the given MIDI note at the given velocity.
        /// </summary>
        /// <param name="note">The note.</param>
        /// <param name="velocity">The velocity.</param>
        public void PlayNote(int note, int velocity)
        {
            MidiNote midiNote = new MidiNote(note, velocity);
            ThreadPool.QueueUserWorkItem(InternalPlayNote, midiNote);
        }

        /// <summary>
        /// Internal function to play a MIDI note. This function is called by the threads in the ThreadPool.
        /// </summary>
        /// <param name="midiNote">The midi note.</param>
        private void InternalPlayNote(object midiNote)
        {
            MidiNote note = (MidiNote)midiNote;
            if (outDevice != null)
            {
                outDevice.Send(new ChannelMessage(ChannelCommand.NoteOn, midiChannel, note.Note, note.Velocity));
                Thread.Sleep(250);
                outDevice.Send(new ChannelMessage(ChannelCommand.NoteOff, midiChannel, note.Note, 0));
            }
        }

        #region IDisposable Members

        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        public void Dispose()
        {
            if (outDevice != null)
            {
                outDevice.Dispose();
            }
        }

        #endregion
    }
}
